// 提供一个异步同时等待的方法

import { createHash } from "crypto";
import { CacheMoudle } from "./CacheMoudle";

class AwaitUnit {
    cbFunc: () => Promise<any>;
    clear: () => void;
    constructor(cb: () => Promise<any>, clear: () => void) {
        this.cbFunc = cb;
        this.clear = clear;
    }

    private running: boolean = false;
    private resloves: any[] = [];
    private rejects: any[] = [];

    call<T>() {
        return new Promise<T>((resolve, reject) => {
            this.resloves.push(resolve);
            this.rejects.push(reject);

            if (!this.running) {
                this.running = true;
                this.cbFunc().then((v) => {
                    this.finishCall(null, v);
                }).catch(e => {
                    this.finishCall(e, null);
                })
            }
        })
    }

    private async finishCall(err: any, result: any) {
        if (err) {
            for (let i = 0; i < this.rejects.length; i++) {
                this.rejects[i](err);
            }
        }
        else {
            for (let i = 0; i < this.resloves.length; i++) {
                this.resloves[i](result);
            }
        }
        this.running = false;
        this.rejects.splice(0, this.rejects.length);
        this.resloves.splice(0, this.rejects.length);
        this.clear();
    }
}

var cache = CacheMoudle.createCache<AwaitUnit>("awaitCall", 10 * 1000);

function awaitCall<T>(name: string, cbFunc: () => Promise<any>) {
    if (!cache.has(name)) {
        cache.set(name, new AwaitUnit(cbFunc, function () {
            cache.del(name);
        }));
    }
    let t = cache.get<AwaitUnit>(name) as AwaitUnit;
    return t.call<T>()
}

/**
 * 等待完成后的再执行
 * @param posList 一致性检查需要的参数列表 不填表示全部参与
 */
export function AwaitCall(posList: number[] = []): MethodDecorator {
    return doAwaitCall.bind(undefined, posList)
}

function doAwaitCall(posList: number[], target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>): void | TypedPropertyDescriptor<any> {
    let tv = descriptor.value;
    descriptor.value = function (...args: any[]) {
        let _this = this;
        let key = propertyKey.toString();
        if (posList.length == 0) {
            key += createHash("md5").update(JSON.stringify(args)).digest("hex")
        }
        else {
            let useFullList = [];
            for (let i = 0; i < posList.length; i++) {
                useFullList.push(args[posList[i]])
            }
            key += createHash("md5").update(JSON.stringify(useFullList)).digest("hex")
        }

        return awaitCall(key, function () {
            return tv.apply(_this, args)
        })
    }
}